
Day 6 談到,一整段 User Journey 裡反覆累積的等待,已經逼著系統重新評估 GAS + Sheets 的執行模型。
架構開始移動之後,另一個比「速度」更根本的問題也浮了出來:
同一個人,可以從不同入口進來。
有人從員工編號登入。
有人從 LINE 進來。
也有人今天用員編,明天又從 LINE 打開。
一開始,這件事很容易被理解成:
系統多支援一種登入方式。
但真的踩過 Bug 之後才會發現,問題不在「多一種登入」,而在「多個入口最後是否收斂到同一個人」。
因為對便當系統來說,登入方式只是入口。
訂單、餘額、角色與歷史需要知道的是:
不管你從哪裡進來,系統最後認得的是不是同一個人?
這也是這套便當系統第一次碰到「Identity」問題。
如果只看登入畫面,事情很單純。
員工編號
→ 登入成功
LINE
→ 登入成功
兩條路都能走進系統,看起來就完成了。
但只要系統開始保存狀態,問題就會立刻變成:
員編 123456
LINE User A
到底是:
兩個使用者?
還是:
同一個人的兩種入口?
如果這件事沒有回答清楚,後面的每個功能都可能出問題。
例如:
因此,這三件事需要被拆開看:
| 層次 | 它回答的問題 |
|---|---|
| Authentication | 你這次是怎麼證明自己可以進來? |
| Identity Resolution | 這個入口對應到系統裡哪一個人? |
| Onboarding | 如果還缺資料,要補完哪些步驟才能開始使用? |
這三件事很容易被塞進同一段登入流程。
但它們其實不是同一件事。
這套模型最容易理解的方式是:
員編與 LINE 都不是使用者本身,它們只是用來找到使用者的入口。
也就是:
員編登入 ─┐
├─→ canonical user
LINE 登入 ─┘
canonical user 才是系統內應該承擔責任的身份。
訂單屬於它。
餘額屬於它。
一般使用者角色屬於它。
歷史也應該回到它。
這樣一來,登入方式就變成:
如何找到同一個 canonical user。
而不是:
每增加一種登入方式,就增加一種 User。
這個差別看起來只是資料模型的文字遊戲,但實際上會直接決定系統會不會產生重複身份。

不同登入入口最後都應收斂到同一個 canonical user;訂單、餘額、權限與歷史跟著「人」走,而不是跟著某一種登入方式走。
這套身份邏輯後來曾經出現一個很典型的問題。
新的 Normal User 第一次用員工編號進來後,系統其實已經建立了對應的 canonical user,也有 employee guest session。
照需求來說,這時候一般使用者應該可以直接進入主畫面。
LINE 綁定可以之後再做。
但實際流程卻曾經繼續把使用者導向 LINE 綁定。
表面看起來很像:
員編登入沒有真的完成。
只看現象,很容易直接往 Backend 查:
但 bounded trace 後看到的結果不是這樣。
問題出在前端狀態映射。
某個狀態組合是:
employee_guest
+
user = null
它被前端解讀成:
尚未註冊
→ 繼續走 LINE 綁定
但這個判斷把兩個不同問題混在一起了。
employee_guest 描述的是:
這次從哪一種 authentication entry 進來。
而 user = null 在那個流程節點,也不能單獨代表:
系統裡沒有 canonical user。
結果就是:
Backend 的身份關係其實可以成立,Frontend 卻把中間狀態翻譯成錯的使用者旅程。
這次修正後,一般 Normal User 可以用員編或 LINE 其中一種方式進入;LINE 綁定不再是員編首次登入的強制前置條件。
如果只是描述結果,它很容易被寫成:
修正員編登入後錯誤跳轉 LINE
更值得留下來的工程結論是另一件事:
Identity Bug 不一定發生在「驗證身份」的那一層。
它可能發生在:
任何一層。
而且它們在畫面上的症狀可能非常像。
使用者看到的都只是:
我怎麼又被叫去登入?
因此,遇到身份問題時,第一個問題不該是:
哪一段登入程式要改?
而是先把 trace 拆開。
遇到這類問題時,可以先畫出一條很簡單的鏈:
Entry
↓
Authentication
↓
Session
↓
Canonical User Resolution
↓
Bootstrap / Identity State
↓
Frontend Routing
↓
Main UI / Onboarding
然後逐層問:
例如:
employee ID
LINE
這只是入口。
先不要把它直接等同於 User。
例如:
這一層只回答:
這個 entry 可不可以成立?
如果 session 根本沒留下來,後面當然全部會失敗。
但如果 session 正常,就不要一直重查 Authentication。
這是整條線最重要的一層。
要確認:
employee entry
→ user X
LINE entry
→ user X
而不是:
employee entry
→ user X
LINE entry
→ user Y
這一層很容易開始出現:
之類的語意。
問題是這些狀態必須有清楚 contract。
這次 Bug 留下的提醒是:
Backend 回對,不代表畫面一定走對。
只要前端自己多推論一步,就可能重新發明一套身份規則。
身份系統最危險的狀態,不一定是少一個判斷。
有時候反而是判斷太多。
例如:
Worker 判斷 canonical user
Frontend 再看 authMode 猜一次
Bootstrap component 再看 user 是否為 null 猜一次
Routing 再猜一次該不該去 onboarding
每一段單看都很合理。
但只要它們對同一個狀態的理解不完全一樣,就會出現:
多個地方都在回答同一個問題,最後卻給出不同答案。
因此,更穩定的做法是把責任拆清楚:
| 問題 | 應該由誰主要回答 |
|---|---|
| 這次如何登入? | Authentication |
| 這個入口對應哪個 User? | Identity Resolution |
| 使用者目前缺什麼? | Identity / Onboarding State |
| 畫面接下來去哪? | Frontend 依 contract 呈現 |
Frontend 可以決定 UX。
但不應該在沒有 authority 的情況下重新推導 canonical identity。
這是 Day 7 最重要的判斷框架。
假設使用者第一次從員編登入:
Authentication
= 成功
這不等於:
Identity
= 一定是新 User
也不等於:
Onboarding
= 必須綁 LINE
三者要分開。
例如一般使用者目前的策略可以理解成:
員編
↓
找到 / 建立 canonical user
↓
完成必要 onboarding
↓
可進入一般功能
LINE binding
= optional enhancement
而不是:
員編
↓
一定要 LINE
↓
才算完整 User
這個差異很重要。
因為如果把某一種 authentication method 寫成整個 Identity lifecycle 的必經之路,未來每次調整登入政策都會牽動整套使用者模型。
做到這裡,很容易走到另一個極端:
既然員編和 LINE 都只是入口,那全部都放寬就好了。
也不對。
Identity Resolution 越自由,越需要處理:
這套便當系統目前也刻意區分一般 User 與更高權限角色。
一般使用者可以讓員編與 LINE 比較彈性地指向同一 canonical user。
但 Admin / ProxyAdmin 的身份與授權仍需要更嚴格的保護。
所以 Day 7 的結論不是:
登入越方便越好。
而是:
入口可以有很多個,但每一個入口如何收斂到 canonical user,必須有單一而清楚的規則。
方便是 UX。
身份一致性與權限安全是另一個責任。
兩者不能混成一句「登入成功就好了」。
這裡也要保留一個邊界。
不是所有小系統一開始都需要:
如果一個系統永遠只有單一入口,而且沒有跨入口共用資料的需求,直接使用簡單登入可能完全足夠。
需要正式建模 Identity 的訊號,通常是:
當這些事情開始出現,再把 canonical user 與狀態 contract 拉出來,通常比一開始就建立完整身份平台更合理。
Day 5 的多店家留下了一個結論:
原本藏在環境裡的常數,最後會變成 Domain Data。
Day 6 的等待則證明:
功能可以完成,不代表整段 User Journey 已經足夠好。
到了 Day 7,身份系統再把問題往前推了一層。
一開始「你是誰」好像只是登入畫面的問題。
但只要訂單、餘額、權限與歷史都要跟著同一個人走,
它就會從:
登入方式
一路長成:
Authentication
↓
Identity Resolution
↓
Canonical User
↓
Onboarding State
↓
Authorization / Domain Data
這套系統不是因為想做 Identity Platform 才變複雜。
而是當真實使用者開始從不同入口進來之後,
原本那句:
「登入成功就好。」
已經不夠回答系統需要知道的事。
要回答的是:
不管你從哪裡進來,我能不能確定你仍然是同一個人?
這件事一旦答錯,壞掉的不只是一個登入畫面。
後面的訂單、餘額、權限與歷史,都可能跟著認錯人。
這不是純概念示範,而是一套持續開發中的便當訂購系統。文章著重「為什麼」,GitHub 保留實際程式碼、文件與演進紀錄。
GitHub:https://github.com/henryfir456/bento-order-app
身份回答的是:
這筆資料到底屬於誰?
但便當系統裡還有另一種資料,一旦開始出現,就不能只把它當成普通欄位。
那就是:
錢。
當「目前餘額」和一筆一筆的儲值、扣款、調整同時存在時,我也開始碰到另一個問題:
到底哪一份資料才算帳?